<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h3 data-lake-id="ThxHj" id="ThxHj"><span data-lake-id="u93173f48" id="u93173f48">问题发现</span></h3>
  <p data-lake-id="uf0d08847" id="uf0d08847"><br></p>
  <p data-lake-id="u3bceb940" id="u3bceb940"><span data-lake-id="u0e966a3c" id="u0e966a3c">我们有一个应用，平常都好好的，运行的都比较平稳，但是每次应用在发布过程中，刚刚重启好的机器经常会有cpu利用率和load飙高的现象，进而导致我们应用的RT变高，很多调用方反馈有大量超时。 </span></p>
  <p data-lake-id="ueead6c6f" id="ueead6c6f"><span data-lake-id="u9e5bb971" id="u9e5bb971">​</span><br></p>
  <p data-lake-id="u8c5ff445" id="u8c5ff445"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1683538843167-4a3a6c11-ca65-4e06-9f2e-c3e38c6deb5c.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_46%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u936d4e77" id="u936d4e77"><br></p>
  <p data-lake-id="uc002eef8" id="uc002eef8"><span data-lake-id="ub0c29a0c" id="ub0c29a0c">应用刚刚发布后的几分钟内，CPU飙高到70%，Load飙高到11，并持续几分钟后就好了。</span></p>
  <p data-lake-id="u770ef46b" id="u770ef46b"><br></p>
  <h3 data-lake-id="vauio" id="vauio"><span data-lake-id="u7da89d5a" id="u7da89d5a">问题定位</span></h3>
  <p data-lake-id="u4d8efcdf" id="u4d8efcdf"><br></p>
  <p data-lake-id="uc389c625" id="uc389c625"><span data-lake-id="uab01d4e6" id="uab01d4e6">这个问题的排查过程挺复杂的，刚开始怀疑是应用代码的问题，但是经过多方检查，打印各种堆、栈的dump，分析各种火焰图，都没有看到有什么异常。</span></p>
  <p data-lake-id="ufa5a2b0a" id="ufa5a2b0a"><span data-lake-id="ue3574c8e" id="ue3574c8e">​</span><br></p>
  <p data-lake-id="uea39a906" id="uea39a906"><span data-lake-id="u669630d3" id="u669630d3">后来又怀疑虚拟机、容器、宿主机是不是有问题，前后换了机器配置、docker镜像都没有什么变化。</span></p>
  <p data-lake-id="u61493f30" id="u61493f30"><span data-lake-id="u98c38edc" id="u98c38edc">​</span><br></p>
  <p data-lake-id="ued8e2c69" id="ued8e2c69"><span data-lake-id="ud82ab1b2" id="ud82ab1b2">后来有怀疑JDK的版本、堆内存设置、垃圾收集器等可能有关？但是排查下来也都发现并没有什么特别的问题。</span></p>
  <p data-lake-id="u0aa08cc8" id="u0aa08cc8"><span data-lake-id="u45fdbc5e" id="u45fdbc5e">​</span><br></p>
  <p data-lake-id="u1a70c912" id="u1a70c912"><span data-lake-id="u7dee4107" id="u7dee4107">最后，经过多方讨论，定位到和JIT优化有关。</span></p>
  <p data-lake-id="u444f58b2" id="u444f58b2"><span data-lake-id="ubd27bb80" id="ubd27bb80">​</span><br></p>
  <p data-lake-id="ubbeceb30" id="ubbeceb30"><span data-lake-id="u1f9c4253" id="u1f9c4253">我们知道, Java最开始是一种解释型语言，他的代码需要先通过javac编译成class文件，然后再通过解释器将字节码将其翻译成对应的机器指令, 逐条读入, 逐条解释翻译。</span></p>
  <p data-lake-id="ueaf9d570" id="ueaf9d570"><span data-lake-id="u0a965c84" id="u0a965c84">​</span><br></p>
  <p data-lake-id="u890b9eb2" id="u890b9eb2"><span data-lake-id="u3a71fbb7" id="u3a71fbb7">但是这个过程太慢了，于是hotspot提出了JIT优化，JIT优化器会基于热点代码检测，把热点代码直接翻译成机器语言，方便后续直接执行。大大提升了效率。</span></p>
  <p data-lake-id="uaa8126bb" id="uaa8126bb"><span data-lake-id="u91ebe676" id="u91ebe676">​</span><br></p>
  <p data-lake-id="u8b2352be" id="u8b2352be"><br></p>
  <p data-lake-id="u82aec2cc" id="u82aec2cc"><span data-lake-id="ud4ff78dc" id="ud4ff78dc">理解了JIT编译的原理之后，其实可以知道，</span><strong><span data-lake-id="ube38cb30" id="ube38cb30">JIT优化是在运行期进行的</span></strong><span data-lake-id="uebfd7de6" id="uebfd7de6">，并且也不是Java进程刚一启动就能优化的，是需要先执行一段时间的，因为他需要先知道哪些是热点代码。</span></p>
  <p data-lake-id="udc18b19c" id="udc18b19c"><span data-lake-id="u71c0e437" id="u71c0e437">​</span><br></p>
  <p data-lake-id="uf1c1c439" id="uf1c1c439"><span data-lake-id="u67a8c2b5" id="u67a8c2b5">所以，在JIT优化开始之前，我们的所有请求，都是要经过解释执行的，这个过程就会相对慢一些。</span></p>
  <p data-lake-id="u3703d4b5" id="u3703d4b5"><span data-lake-id="ud7f80ada" id="ud7f80ada">​</span><br></p>
  <p data-lake-id="ua4781124" id="ua4781124"><span data-lake-id="u315e31c3" id="u315e31c3">而且，如果你们的应用的请求量比较大的的话，这种问题就会更加明显，在应用启动过程中，会有大量的请求过来，这就会导致解释器持续的在努力工作。</span></p>
  <p data-lake-id="u4605bff8" id="u4605bff8"><span data-lake-id="u856935b2" id="u856935b2">​</span><br></p>
  <p data-lake-id="u80343efe" id="u80343efe"><strong><span data-lake-id="ua198b3fd" id="ua198b3fd">一旦解释器对CPU资源占用比较大的话，就会间接的导致CPU、LOAD等飙高，导致应用的性能进一步下降。</span></strong></p>
  <p data-lake-id="ua7816aed" id="ua7816aed"><span data-lake-id="u89de65bc" id="u89de65bc">​</span><br></p>
  <p data-lake-id="u8a33e206" id="u8a33e206"><strong><span data-lake-id="u27945d3e" id="u27945d3e">这也是为什么很多应用在发布过程中，会出现刚刚重启好的应用会发生大量的超时问题了。</span></strong></p>
  <p data-lake-id="u250aa584" id="u250aa584"><span data-lake-id="u90455254" id="u90455254">​</span><br></p>
  <p data-lake-id="u313c9ee8" id="u313c9ee8"><span data-lake-id="ubde42641" id="ubde42641">而随着请求的不断增多，JIT优化就会被触发，这就是使得后续的热点请求的执行可能就不需要在通过解释执行了，直接运行JIT优化后缓存的机器码就行了。</span></p>
  <p data-lake-id="u517deb68" id="u517deb68"><br></p>
  <h3 data-lake-id="H5qUF" id="H5qUF"><span data-lake-id="u16487f8c" id="u16487f8c">问题解决</span></h3>
  <p data-lake-id="u8108b4ea" id="u8108b4ea"><br></p>
  <p data-lake-id="ue3a9a16d" id="ue3a9a16d"><span data-lake-id="ud0418500" id="ud0418500">解决这个问题主要有两种思路：</span></p>
  <p data-lake-id="u13e215b3" id="u13e215b3"><span data-lake-id="uc7a9e1aa" id="uc7a9e1aa">​</span><br></p>
  <p data-lake-id="u3c55bc70" id="u3c55bc70"><span data-lake-id="u152cf486" id="u152cf486">1、提升JIT优化的效率</span></p>
  <p data-lake-id="u2d4415bc" id="u2d4415bc"><span data-lake-id="u6fc54bff" id="u6fc54bff">​</span><br></p>
  <p data-lake-id="ue62c9ae2" id="ue62c9ae2"><span data-lake-id="u0df5d93d" id="u0df5d93d">2、降低瞬时请求量</span></p>
  <p data-lake-id="ud7f105a4" id="ud7f105a4"><span data-lake-id="uea1de88c" id="uea1de88c">​</span><br></p>
  <p data-lake-id="u455ce459" id="u455ce459"><span data-lake-id="u744fc804" id="u744fc804">​</span><br></p>
  <p data-lake-id="u6327dd8d" id="u6327dd8d"><span data-lake-id="u7458726f" id="u7458726f">在提升JIT优化效率的设计上，大家可以了解一下阿里研发的JDK——Dragonwell。</span></p>
  <p data-lake-id="u96f87b51" id="u96f87b51"><span data-lake-id="u5a97f74f" id="u5a97f74f">​</span><br></p>
  <p data-lake-id="u42542ea0" id="u42542ea0"><span data-lake-id="uf5a4a73b" id="uf5a4a73b">这个相比OpenJDK提供了一些专有特性，其中一项叫做</span><strong><span data-lake-id="u7f94eac8" id="u7f94eac8">JwarmUp</span></strong><span data-lake-id="ua77e7258" id="ua77e7258">的技术就是解决JIT优化效率的问题的。</span></p>
  <p data-lake-id="u58640b20" id="u58640b20"><span data-lake-id="u5f7838af" id="u5f7838af">​</span><br></p>
  <p data-lake-id="ubdfbf51b" id="ubdfbf51b"><span data-lake-id="ue9c47a96" id="ue9c47a96">这个技术主要是通过记录Java应用上一次运行时候的编译信息到文件中，在下次应用启动时，读取该文件，从而在流量进来之前，提前完成类的加载、初始化和方法编译，从而跳过解释阶段，直接执行编译好的机器码。</span></p>
  <p data-lake-id="ucba75d77" id="ucba75d77"><span data-lake-id="uca49a62d" id="uca49a62d">​</span><br></p>
  <p data-lake-id="u589bc8d9" id="u589bc8d9"><span data-lake-id="u58bf9e36" id="u58bf9e36">除了针对JDK做优化之外，还可以采用另外一种方式来解决这个问题，那就是做预热。</span></p>
  <p data-lake-id="ue55ad071" id="ue55ad071"><span data-lake-id="u74a9e7e5" id="u74a9e7e5">​</span><br></p>
  <p data-lake-id="ue3bfd05e" id="ue3bfd05e"><span data-lake-id="udf9e9531" id="udf9e9531">很多人都听说过缓存预热，其实思想是类似的。</span></p>
  <p data-lake-id="u1917ffb2" id="u1917ffb2"><span data-lake-id="uc757ec6e" id="uc757ec6e">​</span><br></p>
  <p data-lake-id="u3faff689" id="u3faff689"><span data-lake-id="u8084345e" id="u8084345e">就是说在应用刚刚启动的时候，通过调节负载均衡，不要很快的把大流量分发给他，而是先分给他一小部分流量，通过这部分流量来触发JIT优化，等优化好了之后，再把流量调大。</span></p>
  <p data-lake-id="ueed88fe1" id="ueed88fe1"><span data-lake-id="u56218e82" id="u56218e82">​</span><br></p>
  <p data-lake-id="u30a1c9b4" id="u30a1c9b4"><span data-lake-id="u42da3104" id="u42da3104">我们内部有一个配置开关，可以帮助我们开启JwarmUp，配置上之后，就发现问题基本解决了。</span></p>
  <p data-lake-id="u694c82fe" id="u694c82fe"><span data-lake-id="u10f5940e" id="u10f5940e">​</span><br></p>
  <p data-lake-id="ue1d0e1bd" id="ue1d0e1bd"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1683539410575-5c34b98d-97da-4b1c-9979-c4d0fffc67b1.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_20%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u334139fc" id="u334139fc"><span data-lake-id="uc9c79a9e" id="uc9c79a9e">​</span><br></p>
 </body>
</html>